package com.codeanalysissys.backend.golang;

import com.codeanalysissys.backend.golang.parser.GoLexer;
import com.codeanalysissys.backend.python.parser.PythonLexer;
import ghaffarian.graphs.*;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Paths;
import java.util.*;


import com.codeanalysissys.backend.golang.parser.GoParser;
import com.codeanalysissys.backend.graphs.pdg.CDEdge;
import com.codeanalysissys.backend.graphs.pdg.ControlDependenceGraph;
import com.codeanalysissys.backend.graphs.pdg.PDNode;
import org.antlr.v4.runtime.*;
import org.antlr.v4.runtime.misc.Interval;
import org.antlr.v4.runtime.tree.ParseTree;
import com.codeanalysissys.backend.graphs.cfg.CFEdge;
import com.codeanalysissys.backend.graphs.cfg.CFNode;
import com.codeanalysissys.backend.graphs.cfg.ControlFlowGraph;
import com.codeanalysissys.backend.golang.parser.GoParserBaseVisitor;
import ghaffarian.nanologger.Logger;

/**
 * A Control Dependence Graph (CDG) builder for Go programs.
 * A Go parser generated via ANTLRv4 is used for this purpose.
 * This implementation is based on ANTLRv4's Visitor pattern.
 *
 * @author Huang WeiQuan
 */

public class GoCDGBuilder {
    public static ControlDependenceGraph build(InputStream inFile) throws IOException {
        ANTLRInputStream input = new ANTLRInputStream(inFile);
        GoLexer lexer = new GoLexer(input);
        CommonTokenStream tokens = new CommonTokenStream(lexer);
        GoParser parser = new GoParser(tokens);
        ParseTree tree = parser.sourceFile();
        ControlDependenceGraph cdg = new ControlDependenceGraph();
        GoCDGBuilder.ControlDependencyVisitor visitor = new GoCDGBuilder.ControlDependencyVisitor(cdg);
        visitor.visit(tree);
        return cdg;
    }


    public static ControlDependenceGraph build(File goFile) throws IOException {
        if (!goFile.getName().endsWith(".go"))
            throw new IOException("Not a Golang File!");
        CharStream input = CharStreams.fromPath(Paths.get(goFile.getAbsolutePath()));
        GoLexer lexer = new GoLexer(input);
        CommonTokenStream tokens = new CommonTokenStream(lexer);
        GoParser parser = new GoParser(tokens);
        ParseTree tree = parser.sourceFile();
        Logger.debug("CTRL DEP ANALYSIS: " + goFile.getPath());
        ControlDependenceGraph cdg = new ControlDependenceGraph(goFile.getName());
        GoCDGBuilder.ControlDependencyVisitor visitor = new GoCDGBuilder.ControlDependencyVisitor(cdg);
        visitor.visit(tree);
        return cdg;
    }

    private static class ControlDependencyVisitor extends GoParserBaseVisitor<Void>{

        private ControlDependenceGraph cdg;
        private Deque<PDNode> ctrlDeps;
        private Deque<PDNode> negDeps;
        private Deque<Integer> jmpCounts;
        private Deque<PDNode> jumpDeps;
        private boolean buildRegion;
        private boolean follows;
        private int lastFollowDepth;
        private int regionCounter;
        private int jmpCounter;

        public ControlDependencyVisitor(ControlDependenceGraph cdg) {
            this.cdg = cdg;
            ctrlDeps = new ArrayDeque<>();
            negDeps = new ArrayDeque<>();
            jumpDeps = new ArrayDeque<>();
            jmpCounts = new ArrayDeque<>();
            buildRegion = false;
            follows = true;
            lastFollowDepth = 0;
            regionCounter = 1;
            jmpCounter = 0;
        }

        private void init() {
            ctrlDeps.clear();
            negDeps.clear();
            jumpDeps.clear();
            jmpCounts.clear();
            buildRegion = false;
            follows = true;
            lastFollowDepth = 0;
            regionCounter = 1;
            jmpCounter = 0;
        }

        @Override
        public Void visitFunctionDecl(GoParser.FunctionDeclContext ctx)
        {
            init();
            PDNode entry = new PDNode();
            entry.setLineOfCode(ctx.getStart().getLine());
            String retType = "func";
            String args = getOriginalCodeText(ctx.signature());
            entry.setCode(retType + " " + ctx.IDENTIFIER() + args);
            cdg.addVertex(entry);
            pushCtrlDep(entry);
            if(ctx.block()!=null)
            {
                visit(ctx.block());
            }
            PDNode exit = new PDNode();
            exit.setLineOfCode(0);
            exit.setCode("exit");
            cdg.addVertex(exit);
            cdg.addEdge(new Edge<>(entry, new CDEdge(CDEdge.Type.EPSILON), exit));
            return null;
        }

        @Override
        public Void visitExpressionStmt(GoParser.ExpressionStmtContext ctx) {
            PDNode expr = new PDNode();
            expr.setLineOfCode(ctx.getStart().getLine());
            expr.setCode(getOriginalCodeText(ctx));
            Logger.debug(expr.getLineOfCode() + ": " + expr.getCode());
            addNodeEdge(expr);
            return null;
        }

        @Override
        public Void visitVarDecl(GoParser.VarDeclContext ctx){
            PDNode varDec = new PDNode();
            varDec.setLineOfCode(ctx.getStart().getLine());
            varDec.setCode(getOriginalCodeText(ctx));
            addNodeEdge(varDec);
            return null;
        }

        @Override
        public Void visitSimpleStmt(GoParser.SimpleStmtContext ctx){
            PDNode simpleStat = new PDNode();
            simpleStat.setLineOfCode(ctx.getStart().getLine());
            simpleStat.setCode(getOriginalCodeText(ctx));
            addNodeEdge(simpleStat);
            return null;
        }

        @Override
        public Void visitIfStmt(GoParser.IfStmtContext ctx)
        {
            // 'if' parExpression statement ('else' statement)?
            PDNode ifNode = new PDNode();
            ifNode.setLineOfCode(ctx.getStart().getLine());
            ifNode.setCode("if " + getOriginalCodeText(ctx.expression()));
            addNodeEdge(ifNode);
            //
            PDNode thenRegion = new PDNode();
            thenRegion.setLineOfCode(0);
            thenRegion.setCode("THEN");
            cdg.addVertex(thenRegion);
            cdg.addEdge(new Edge<>(ifNode, new CDEdge(CDEdge.Type.TRUE), thenRegion));
            //
            PDNode elseRegion = new PDNode();
            elseRegion.setLineOfCode(0);
            elseRegion.setCode("ELSE");
            //
            pushCtrlDep(thenRegion);
            negDeps.push(elseRegion);
            visit(ctx.block(0));
            negDeps.pop();
            popCtrlDep(thenRegion);
            //
            if (ctx.block().size() > 1) { // if with else
                follows = false;
                cdg.addVertex(elseRegion);
                cdg.addEdge(new Edge<>(ifNode, new CDEdge(CDEdge.Type.FALSE), elseRegion));
                //
                pushCtrlDep(elseRegion);
                negDeps.push(thenRegion);
                visit(ctx.block(1));
                negDeps.pop();
                popCtrlDep(elseRegion);
            } else if (buildRegion) {
                // there is no else, but we need to add the ELSE region
                cdg.addVertex(elseRegion);
                cdg.addEdge(new Edge<>(ifNode, new CDEdge(CDEdge.Type.FALSE), elseRegion));
            }
            follows = true;
            return null;
        }

        @Override
        public Void visitEmptyStmt(GoParser.EmptyStmtContext ctx)
        {
            //Empty
            return null;
        }

        @Override
        public Void visitForStmt(GoParser.ForStmtContext ctx)
        {
            if (ctx.forClause()!= null) //Common for loop
            {
                PDNode forInit;
                PDNode forExpr, forUpdate;
                if(ctx.forClause().initStmt!=null)
                {
                    forInit = new PDNode();
                    forInit.setLineOfCode(ctx.forClause().initStmt.getStart().getLine());
                    forInit.setCode(getOriginalCodeText(ctx.forClause().initStmt));
                    addNodeEdge(forInit);
                }
                int forExprLine;
                String forExprCode;
                if (ctx.forClause().expression() == null) { // empty for-loop-predicate
                    forExprCode = ";";
                    forExprLine = ctx.getStart().getLine();
                } else {
                    forExprCode = getOriginalCodeText(ctx.forClause().expression());
                    forExprLine = ctx.forClause().expression().getStart().getLine();
                }
                forExpr = new PDNode();
                forExpr.setLineOfCode(forExprLine);
                forExpr.setCode("for (" + forExprCode + ")");
                addNodeEdge(forExpr);
                //
                PDNode loopRegion = new PDNode();
                loopRegion.setLineOfCode(0);
                loopRegion.setCode("LOOP");
                cdg.addVertex(loopRegion);
                cdg.addEdge(new Edge<>(forExpr, new CDEdge(CDEdge.Type.TRUE), loopRegion));
                //
                pushLoopBlockDep(loopRegion);
                visit(ctx.block());
                if (ctx.forClause().postStmt != null) { // non-empty for-update
                    forUpdate = new PDNode();
                    forUpdate.setLineOfCode(ctx.forClause().postStmt.getStart().getLine());
                    forUpdate.setCode(getOriginalCodeText(ctx.forClause().postStmt));
                    // we don't use 'addNodeEdge(forUpdate)' because the behavior of for-update
                    // step is different from other statements with regards to break/continue.
                    cdg.addVertex(forUpdate);
                    cdg.addEdge(new Edge<>(ctrlDeps.peek(), new CDEdge(CDEdge.Type.EPSILON), forUpdate));
                }
                popLoopBlockDep(loopRegion);
            }
            else
            {
                PDNode forExpr = new PDNode();
                if(ctx.expression()!=null)
                {
                    forExpr.setLineOfCode(ctx.expression().getStart().getLine());
                    forExpr.setCode("for (" + getOriginalCodeText(ctx.expression()) + ")");
                }
                else
                {
                    forExpr.setLineOfCode(0);
                    forExpr.setCode("for ");
                }
                addNodeEdge(forExpr);
                //
                PDNode loopRegion = new PDNode();
                loopRegion.setLineOfCode(0);
                loopRegion.setCode("LOOP");
                cdg.addVertex(loopRegion);
                cdg.addEdge(new Edge<>(forExpr, new CDEdge(CDEdge.Type.TRUE), loopRegion));
                //
                pushLoopBlockDep(loopRegion);
                visit(ctx.block());
                popLoopBlockDep(loopRegion);
            }
            return null;
        }

        @Override
        public Void visitSwitchStmt(GoParser.SwitchStmtContext ctx)
        {
            PDNode switchNode = new PDNode();
            switchNode.setLineOfCode(ctx.getStart().getLine());
            switchNode.setCode("switch " + getOriginalCodeText(ctx.exprSwitchStmt().expression()));
            addNodeEdge(switchNode);
            //
            pushLoopBlockDep(switchNode);
            for (GoParser.ExprCaseClauseContext grp: ctx.exprSwitchStmt().exprCaseClause()) {
                // switchBlockStatementGroup :  switchLabel+ blockStatement+
                visitexprSwitchCase(grp.exprSwitchCase(), grp.statementList());
            }
            visitexprSwitchCase(ctx.exprSwitchStmt().exprSwitchCase(), null);
            popLoopBlockDep(switchNode);
            return null;
        }

        /**
         * Visit a chain of consecutive case-statements.
         */
        private PDNode visitexprSwitchCase(List<GoParser.ExprSwitchCaseContext> cases,
                                         List<GoParser.StatementListContext> block) {
            //  switchLabel :  'case' constantExpression ':'  |  'case' enumConstantName ':'  |  'default' ':'
            if (cases.size() == 1 && cases.get(0).getText().startsWith("default")) {
                PDNode defaultStmnt = new PDNode();
                defaultStmnt.setLineOfCode(cases.get(0).getStart().getLine());
                defaultStmnt.setCode(getOriginalCodeText(cases.get(0)));
                addNodeEdge(defaultStmnt);
                if (block != null) {
                    negDeps.push(defaultStmnt);
                    for (GoParser.StatementListContext stmnt : block)
                        visit(stmnt);
                    negDeps.pop();
                }
            } else if (cases.size() > 0) {
                PDNode lastCase =  new PDNode();
                lastCase.setLineOfCode(cases.get(0).getStart().getLine());
                lastCase.setCode(getOriginalCodeText(cases.get(0)));
                addNodeEdge(lastCase);
                //
                PDNode thenRegion = null;
                if (block != null && block.size() > 0) {
                    thenRegion = new PDNode();
                    thenRegion.setLineOfCode(0);
                    thenRegion.setCode("THEN");
                    cdg.addVertex(thenRegion);
                    cdg.addEdge(new Edge<>(lastCase, new CDEdge(CDEdge.Type.TRUE), thenRegion));
                }
                //
                for (GoParser.ExprSwitchCaseContext ctx : cases.subList(1, cases.size())) {
                    PDNode nextCase = new PDNode();
                    nextCase.setLineOfCode(ctx.getStart().getLine());
                    nextCase.setCode(getOriginalCodeText(ctx));
                    cdg.addVertex(nextCase);
                    cdg.addEdge(new Edge<>(lastCase, new CDEdge(CDEdge.Type.FALSE), nextCase));
                    cdg.addEdge(new Edge<>(nextCase, new CDEdge(CDEdge.Type.TRUE), thenRegion));
                    lastCase = nextCase;
                }
                //
                if (block != null) {
                    PDNode elseRegion = new PDNode();
                    elseRegion.setLineOfCode(0);
                    elseRegion.setCode("ELSE");
                    cdg.addVertex(elseRegion); // We have to add the ELSE here, just
                    //                            in case it is needed in the following.
                    pushCtrlDep(thenRegion);
                    negDeps.push(elseRegion);
                    for (GoParser.StatementListContext stmnt : block)
                        visit(stmnt);
                    negDeps.pop();
                    popCtrlDep(thenRegion);
                    //
                    if (buildRegion) {
                        // there was a 'break', so we need to keep the ELSE region
                        cdg.addEdge(new Edge<>(lastCase, new CDEdge(CDEdge.Type.FALSE), elseRegion));
                    } else if (cdg.getOutDegree(elseRegion) == 0)
                        // the ELSE region is not needed, so we remove it
                        cdg.removeVertex(elseRegion);
                }
            }
            return null;
        }

        @Override
        public Void visitLabeledStmt(GoParser.LabeledStmtContext ctx)
        {
            PDNode labelRegion = new PDNode();
            labelRegion.setLineOfCode(ctx.getStart().getLine());
            labelRegion.setCode(ctx.IDENTIFIER() + ": ");
            addNodeEdge(labelRegion);
            pushCtrlDep(labelRegion);
            visit(ctx.statement());
            popCtrlDep(labelRegion);
            return null;
        }

        @Override
        public Void visitReturnStmt(GoParser.ReturnStmtContext ctx)
        {
            // 'return' expression? ';'
            PDNode ret = new PDNode();
            ret.setLineOfCode(ctx.getStart().getLine());
            ret.setCode(getOriginalCodeText(ctx));
            addNodeEdge(ret);
            // NOTE: an important assumption here is that 'return'
            //       is the last statement inside an if-else body
            //       or it's the last statement of the entire method
            if (!negDeps.isEmpty() && ctrlDeps.size() >= lastFollowDepth) {
                jumpDeps.push(negDeps.peek());
                jumpDeps.peek().setProperty("isExit", Boolean.TRUE);
                jumpDeps.peek().setProperty("isJump", Boolean.FALSE);
                lastFollowDepth = ctrlDeps.size();
                buildRegion = true;
            }
            return visitChildren(ctx);
        }

        @Override
        public Void visitBreakStmt(GoParser.BreakStmtContext ctx)
        {
            PDNode brk = new PDNode();
            brk.setLineOfCode(ctx.getStart().getLine());
            brk.setCode(getOriginalCodeText(ctx));
            addNodeEdge(brk);
            if (!negDeps.isEmpty() && negDeps.peek().getCode().startsWith("default"))
                return null; // just ignore it, and do nothing!
            if (!negDeps.isEmpty() && ctrlDeps.size() >= lastFollowDepth) {
                jumpDeps.push(negDeps.peek());
                jumpDeps.peek().setProperty("isExit", Boolean.FALSE);
                jumpDeps.peek().setProperty("isJump", Boolean.TRUE);
                lastFollowDepth = ctrlDeps.size();
                buildRegion = true;
            }
            return null;
        }

        @Override
        public Void visitFallthroughStmt(GoParser.FallthroughStmtContext ctx)
        {
            PDNode fallthroughStmt = new PDNode();
            fallthroughStmt.setLineOfCode(ctx.getStart().getLine());
            fallthroughStmt.setCode(getOriginalCodeText(ctx));
            addNodeEdge(fallthroughStmt);
            return null;
        }

        @Override
        public Void visitDeferStmt(GoParser.DeferStmtContext ctx)
        {
            PDNode deferStmt = new PDNode();
            deferStmt.setLineOfCode(ctx.getStart().getLine());
            deferStmt.setCode(getOriginalCodeText(ctx));
            addNodeEdge(deferStmt);
            return null;
        }

        @Override
        public Void visitSendStmt(GoParser.SendStmtContext ctx)
        {
            PDNode sendStmt = new PDNode();
            sendStmt.setLineOfCode(ctx.getStart().getLine());
            sendStmt.setCode(getOriginalCodeText(ctx));
            addNodeEdge(sendStmt);
            return null;
        }

        @Override
        public Void visitRecvStmt(GoParser.RecvStmtContext ctx)
        {
            PDNode receiveStmt = new PDNode();
            receiveStmt.setLineOfCode(ctx.getStart().getLine());
            receiveStmt.setCode(getOriginalCodeText(ctx));
            addNodeEdge(receiveStmt);
            return null;
        }

        @Override
        public Void visitMethodDecl(GoParser.MethodDeclContext ctx)
        {
            init();
            PDNode entry = new PDNode();
            entry.setLineOfCode(ctx.getStart().getLine());
            String retType = "func";
            String args = getOriginalCodeText(ctx.signature());
            String args2 = getOriginalCodeText(ctx.receiver());
            entry.setCode(retType+ args2+" "+ctx.IDENTIFIER() + args);
            cdg.addVertex(entry);
            pushCtrlDep(entry);
            if(ctx.block()!=null)
            {
                visit(ctx.block());
            }
            PDNode exit = new PDNode();
            exit.setLineOfCode(0);
            exit.setCode("exit");
            cdg.addVertex(exit);
            cdg.addEdge(new Edge<>(entry, new CDEdge(CDEdge.Type.EPSILON), exit));
            return null;
        }

        @Override
        public Void visitContinueStmt(GoParser.ContinueStmtContext ctx)
        {
            // 'continue' Identifier? ';'
            PDNode cnt = new PDNode();
            cnt.setLineOfCode(ctx.getStart().getLine());
            cnt.setCode(getOriginalCodeText(ctx));
            addNodeEdge(cnt);
            // NOTE: an important assumption here is that 'continue'
            //       is the last statement inside an if-else body
            if (!negDeps.isEmpty() && ctrlDeps.size() >= lastFollowDepth) {
                jumpDeps.push(negDeps.peek());
                jumpDeps.peek().setProperty("isExit", Boolean.FALSE);
                jumpDeps.peek().setProperty("isJump", Boolean.TRUE);
                lastFollowDepth = ctrlDeps.size();
                buildRegion = true;
            }
            return null;
        }

        @Override
        public Void visitIncDecStmt(GoParser.IncDecStmtContext ctx)
        {
            PDNode incDec = new PDNode();
            incDec.setLineOfCode(ctx.getStart().getLine());
            incDec.setCode(getOriginalCodeText(ctx));
            addNodeEdge(incDec);
            return null;
        }

        @Override
        public Void visitSelectStmt(GoParser.SelectStmtContext ctx)
        {
            PDNode selectNode = new PDNode();
            selectNode.setLineOfCode(ctx.getStart().getLine());
            selectNode.setCode("select");
            addNodeEdge(selectNode);
            pushLoopBlockDep(selectNode);
            for (GoParser.CommClauseContext grp: ctx.commClause()) {
                // switchBlockStatementGroup :  switchLabel+ blockStatement+
                visitcommCase(grp.commCase(0), grp.statementList(0));
            }
            visitcommCase(ctx.commCase(), null);
            popLoopBlockDep(selectNode);
            return null;
        }

        private PDNode visitcommCase(List<GoParser.CommCaseContext> cases,
                                           List<GoParser.StatementListContext> block)
        {
            //  switchLabel :  'case' constantExpression ':'  |  'case' enumConstantName ':'  |  'default' ':'
            if (cases.size() == 1 && cases.get(0).getText().startsWith("default")) {
                PDNode defaultStmnt = new PDNode();
                defaultStmnt.setLineOfCode(cases.get(0).getStart().getLine());
                defaultStmnt.setCode(getOriginalCodeText(cases.get(0)));
                addNodeEdge(defaultStmnt);
                if (block != null) {
                    negDeps.push(defaultStmnt);
                    for (GoParser.StatementListContext stmnt : block)
                        visit(stmnt);
                    negDeps.pop();
                }
            } else if (cases.size() > 0) {
                PDNode lastCase =  new PDNode();
                lastCase.setLineOfCode(cases.get(0).getStart().getLine());
                lastCase.setCode(getOriginalCodeText(cases.get(0)));
                addNodeEdge(lastCase);
                //
                PDNode thenRegion = null;
                if (block != null && block.size() > 0) {
                    thenRegion = new PDNode();
                    thenRegion.setLineOfCode(0);
                    thenRegion.setCode("THEN");
                    cdg.addVertex(thenRegion);
                    cdg.addEdge(new Edge<>(lastCase, new CDEdge(CDEdge.Type.TRUE), thenRegion));
                }
                //
                for (GoParser.CommCaseContext ctx : cases.subList(1, cases.size())) {
                    PDNode nextCase = new PDNode();
                    nextCase.setLineOfCode(ctx.getStart().getLine());
                    nextCase.setCode(getOriginalCodeText(ctx));
                    cdg.addVertex(nextCase);
                    cdg.addEdge(new Edge<>(lastCase, new CDEdge(CDEdge.Type.FALSE), nextCase));
                    cdg.addEdge(new Edge<>(nextCase, new CDEdge(CDEdge.Type.TRUE), thenRegion));
                    lastCase = nextCase;
                }
                //
                if (block != null) {
                    PDNode elseRegion = new PDNode();
                    elseRegion.setLineOfCode(0);
                    elseRegion.setCode("ELSE");
                    cdg.addVertex(elseRegion); // We have to add the ELSE here, just
                    //                            in case it is needed in the following.
                    pushCtrlDep(thenRegion);
                    negDeps.push(elseRegion);
                    for (GoParser.StatementListContext stmnt : block)
                        visit(stmnt);
                    negDeps.pop();
                    popCtrlDep(thenRegion);
                    //
                    if (buildRegion) {
                        // there was a 'break', so we need to keep the ELSE region
                        cdg.addEdge(new Edge<>(lastCase, new CDEdge(CDEdge.Type.FALSE), elseRegion));
                    } else if (cdg.getOutDegree(elseRegion) == 0)
                        // the ELSE region is not needed, so we remove it
                        cdg.removeVertex(elseRegion);
                }
            }
            return null;
        }

        @Override
        public Void visitGotoStmt(GoParser.GotoStmtContext ctx)
        {
            PDNode gotoStmt = new PDNode();
            gotoStmt.setLineOfCode(ctx.getStart().getLine());
            gotoStmt.setCode(getOriginalCodeText(ctx));
            addNodeEdge(gotoStmt);
            return null;
        }


        /**
         * Add given node to the CD-subgraph and
         * create a new CD-edge based on the last control-dependency.
         */
        private void addNodeEdge(PDNode node) {
            checkBuildFollowRegion();
            cdg.addVertex(node);
            cdg.addEdge(new Edge<>(ctrlDeps.peek(), new CDEdge(CDEdge.Type.EPSILON), node));
        }

        /**
         * Check if a follow-region must be created;
         * if so, create it and push it on the CTRL-dependence stack.
         */
        private void checkBuildFollowRegion() {
            Logger.debug("FOLLOWS = " + follows);
            Logger.debug("BUILD-REGION = " + buildRegion);
            if (buildRegion && follows) {
                PDNode followRegion = new PDNode();
                followRegion.setLineOfCode(0);
                followRegion.setCode("FOLLOW-" + regionCounter++);
                cdg.addVertex(followRegion);
                // check to see if there are any exit-jumps in the current chain
                followRegion.setProperty("isJump", Boolean.TRUE);
                for (PDNode dep: jumpDeps)
                    if ((Boolean) dep.getProperty("isExit")) {
                        followRegion.setProperty("isJump", Boolean.FALSE);
                        followRegion.setProperty("isExit", Boolean.TRUE);
                    }
                if ((Boolean) followRegion.getProperty("isJump"))
                    ++jmpCounter;
                // connect the follow-region
                if (Boolean.TRUE.equals(jumpDeps.peek().getProperty("isTry"))) {
                    PDNode jmpDep = jumpDeps.pop();
                    if (!cdg.containsVertex(jmpDep))
                        cdg.addVertex(jmpDep);
                    cdg.addEdge(new Edge<>(jmpDep, new CDEdge(CDEdge.Type.NOT_THROWS), followRegion));
                } else {
                    PDNode jmpDep = jumpDeps.pop();
                    if (!cdg.containsVertex(jmpDep))
                        cdg.addVertex(jmpDep);
                    cdg.addEdge(new Edge<>(jmpDep, new CDEdge(CDEdge.Type.EPSILON), followRegion));
                }
                // if the jump-chain is not empty, remove all non-exit jumps
                if (!jumpDeps.isEmpty()) {
                    for (Iterator<PDNode> itr = jumpDeps.iterator(); itr.hasNext(); ) {
                        PDNode dep = itr.next();
                        if (Boolean.FALSE.equals(dep.getProperty("isExit")))
                            itr.remove();
                    }
                }
                lastFollowDepth = 0;
                pushCtrlDep(followRegion);
            }
        }

        /**
         * Push given node to the control-dependency stack.
         */
        private void pushCtrlDep(PDNode dep) {
            ctrlDeps.push(dep);
            buildRegion = false;
        }

        /**
         * Push this loop block region to the control-dependency stack
         * and reset the jumps-counter for this loop-block.
         */
        private void pushLoopBlockDep(PDNode region) {
            pushCtrlDep(region);
            jmpCounts.push(jmpCounter);
            jmpCounter = 0;
        }

        /**
         * Pop out this loop-block region off the control stack
         * and also pop off all jump-dependencies of this block.
         */
        private void popLoopBlockDep(PDNode region) {
            for (Iterator<PDNode> itr = ctrlDeps.iterator(); jmpCounter > 0 && itr.hasNext(); ) {
                // NOTE: This iteration works correctly, even though ctrlDeps is a stack.
                //       This is due to the Deque implementation, which removes in LIFO.
                PDNode dep = itr.next();
                if (Boolean.TRUE.equals(dep.getProperty("isJump"))) {
                    itr.remove();
                    --jmpCounter;
                }
            }
            jmpCounter = jmpCounts.pop();
            lastFollowDepth = 0;
            popCtrlDep(region);
        }

        /**
         * Pop out the last dependency off the stack and
         * set the 'buildRegion' flag if necessary.
         */
        private void popCtrlDep(PDNode dep) {
            ctrlDeps.remove(dep); //ctrlDeps.pop();
            buildRegion = !jumpDeps.isEmpty();
        }

        /**
         * Get the original program text for the given parser-rule context.
         * This is required for preserving whitespaces.
         */
        private String getOriginalCodeText(ParserRuleContext ctx) {
            int start = ctx.start.getStartIndex();
            int stop = ctx.stop.getStopIndex();
            Interval interval = new Interval(start, stop);
            return ctx.start.getInputStream().getText(interval);
        }
    }
}
